﻿/* HEADER
 * ------
 * © 2009 by Salomon Zwecker 
 * modified by:
 * - 
 */
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;


namespace Shapes.Geometry
{
    /// <summary>
    /// a triangle shape
    /// </summary>
    public sealed class Triangle : Shape, ILinedShape
    {
        internal Vector2 _Point1, _Point2, _Point3;
        internal Line _Line1, _Line2, _Line3;

        /// <summary>
        /// Creates a triangle shape
        /// </summary>
        /// <param name="corner1">the first corner</param>
        /// <param name="corner2">the second corner</param>
        /// <param name="corner3">the third corner</param>
        public Triangle(Vector2 corner1, Vector2 corner2, Vector2 corner3) 
            : base()
        {
            ChangePoints(corner1, corner2, corner3);  
        }

        /// <summary>
        /// Reinitializes the triangle with new points
        /// </summary>
        /// <param name="corner1">the first corner</param>
        /// <param name="corner2">the second corner</param>
        /// <param name="corner3">the third corner</param>
        public void ChangePoints(Vector2 corner1, Vector2 corner2, Vector2 corner3)
        {
            Vector2 pos = new Vector2();
            pos.X = Math.Min(corner1.X, Math.Min(corner2.X, corner3.X));
            pos.Y = Math.Min(corner1.Y, Math.Min(corner2.Y, corner3.Y));
            pos -= _Transform._Origin;
            _Transform._Position = pos;

            _Point1 = corner1 - pos;
            _Point2 = corner2 - pos;
            _Point3 = corner3 - pos;

            if (_Line1 != null) _Line1.Dispose();
            if (_Line2 != null) _Line2.Dispose();
            if (_Line3 != null) _Line3.Dispose();

            _Line1 = new Line(_Point1, _Point2);
            _Line2 = new Line(_Point2, _Point3);
            _Line3 = new Line(_Point3, _Point1);

            _BorderLength = _Line1.BorderLength + _Line2.BorderLength + _Line3.BorderLength;

            Vector2 dim;
            dim.X = Math.Max(_Point1.X, Math.Max(_Point2.X, _Point3.X));
            dim.Y = Math.Max(_Point1.Y, Math.Max(_Point2.Y, _Point3.Y));

            _Transform._Width = dim.X;
            _Transform._Height = dim.Y;
            _Transform._IsDirty = true;
        }

        internal override bool IsPointInside(ref Vector2 point)
        {
            Vector2 uv;
            GetUV(ref point, ref _Point1, ref _Point2, ref _Point3, out uv);

            if ((uv.X < 0 || uv.Y < 0) || (uv.X + uv.Y > 1))
                return false;

            return true;
        }
        /// <summary>
        /// a test to check for the distance of a point to the nearest edge
        /// </summary>
        /// <param name="point">the point to check</param>
        /// <returns>the distance to the nearest edge</returns>
        internal override float GetDistanceToEdge(ref Vector2 point)
        {
            float distU = _Line1.GetDistanceToEdge( point);
            float distV = _Line2.GetDistanceToEdge( point);
            float distW = _Line3.GetDistanceToEdge( point);

            if ((distU <= distV) && (distU <= distW))
                return distU;

            if ((distV <= distU) && (distV <= distW))
                return distV;

            return distW;
        }

        internal override Vector2 GetNearestPointOnEdge(ref Vector2 point)
        {

            //float distU = _Line1.GetDistanceToEdge( point);
            //float distV = _Line2.GetDistanceToEdge( point);
            //float distW = _Line3.GetDistanceToEdge( point);

            //if ((distU <= distV) && (distU <= distW))
            //    return _Line1.GetNearestPointOnEdge( point);

            //if ((distV <= distU) && (distV <= distW))
            //    return _Line2.GetNearestPointOnEdge( point);

            return GetClosestLine(ref point).GetNearestPointOnEdge( point);
        }

        internal override Vector2 GetPositionFromT(float t)
        {
            t *= _BorderLength;
            if (t < _Line1.BorderLength)
            {
                return _Line1.GetPositionFromEdgePath(t / _Line1.BorderLength);
            }
            else if (t < _Line1.BorderLength + _Line2.BorderLength)
            {
                return _Line2.GetPositionFromEdgePath((t - _Line1.BorderLength) / _Line2.BorderLength);
            }
            else
            {
                return _Line3.GetPositionFromEdgePath((t - _Line2.BorderLength - _Line1.BorderLength) / _Line3.BorderLength);
            }
        }

        internal override float GetEdgePathValueFromPoint(ref Vector2 point)
        {
            float distU = _Line1.GetDistanceToEdge(point);
            float distV = _Line2.GetDistanceToEdge(point);
            float distW = _Line3.GetDistanceToEdge(point);

            float length = 0;

            if (distU < distV && distU < distW)
            {
                length = _Line1.BorderLength * _Line1.GetEdgePathValueFromPoint(ref point);
            }
            else if (distV < distW)
            {
                length = _Line1.BorderLength + _Line2.BorderLength * _Line2.GetEdgePathValueFromPoint(ref point);
            }
            else
            {
                length = _Line1.BorderLength + _Line2.BorderLength + _Line3.BorderLength * _Line3.GetEdgePathValueFromPoint(ref point);
            }

            return length / _BorderLength;
        }

        internal override Vector2 GetTangent(ref Vector2 position)
        {
            return GetClosestLine(ref position).GetTangent(position);
        }

        internal override Vector2 GetNormal(ref Vector2 position)
        {
            Vector2 tangent = GetTangent(ref position);
            return new Vector2(-tangent.Y, tangent.X);
        }

        private Line GetClosestLine(ref Vector2 position)
        {
            float distU = _Line1.GetDistanceToEdge(position);
            float distV = _Line2.GetDistanceToEdge(position);
            float distW = _Line3.GetDistanceToEdge(position);

            if ((distU <= distV) && (distU <= distW))
                return _Line1;

            if ((distV <= distU) && (distV <= distW))
                return _Line2;

            return _Line3;
        }

        static void GetUV(ref Vector2 point, ref Vector2 cornerA, ref Vector2 cornerB, ref Vector2 cornerC, out Vector2 result)
        {

            // Compute vectors        
            Vector2 v0 = new Vector2();
            v0.X = cornerC.X - cornerA.X;
            v0.Y = cornerC.Y - cornerA.Y;

            Vector2 v1 = new Vector2();
            v1.X = cornerB.X - cornerA.X;
            v1.Y = cornerB.Y - cornerA.Y;

            Vector2 v2 = new Vector2();
            v2.X = point.X - cornerA.X;
            v2.Y = point.Y - cornerA.Y;

            // Compute dot products
            float dot00 = Vector2.Dot(v0, v0);
            float dot01 = Vector2.Dot(v0, v1);
            float dot02 = Vector2.Dot(v0, v2);
            float dot11 = Vector2.Dot(v1, v1);
            float dot12 = Vector2.Dot(v1, v2);

            // Compute barycentric coordinates
            float invDenom = 1 / (dot00 * dot11 - dot01 * dot01);

            //Vector2 result = new Vector2();
            result = new Vector2(
                (dot11 * dot02 - dot01 * dot12) * invDenom,
                (dot00 * dot12 - dot01 * dot02) * invDenom);
        }
        
        /// <summary>
        /// Clones the Triangle.
        /// </summary>
        /// <returns>an object with the type Triangle</returns>
        public override object Clone()
        {
            Triangle t = new Triangle(_Point1, _Point2, _Point3);
            t._Transform = (Transformation2D)_Transform.Clone();
            return t;
        }
        /// <summary>
        /// Releases all references inside of the class.
        /// </summary>
        public override void Dispose()
        {
            _Line1.Dispose();
            _Line1 = null;

            _Line2.Dispose();
            _Line2 = null;

            _Line3.Dispose();
            _Line3 = null;

            base.Dispose();
        }


        /// <summary>
        /// Gets the enumeration type which is mapped to this kind of object
        /// </summary>
        /// <returns>the geometry typee of this object</returns>
        public override GeometryType GetGeometryType()
        {
            return GeometryType.Triangle;
        }

        /// <summary>
        /// Iterates through all Lines of the object
        /// </summary>
        /// <returns>every line the object contains</returns>
        public IEnumerable<Line> GetLines()
        {
            yield return _Line1;
            yield return _Line2;
            yield return _Line3;
        }
        /// <summary>
        /// The global position of the first lines start point
        /// </summary>
        public Vector2 StartPoint
        {
            get { return Transform.TransformLocalToGlobal(_Line1.StartPoint); }
        }

        /// <summary>
        /// The amount of Lines describing the triangle (3)
        /// </summary>
        public int LineCount
        {
            get { return 3; }
        }

        /// <summary>
        /// converts the geometry to a LineStrip.
        /// </summary>
        /// <returns>a line strip object</returns>
        public override LineStrip ToLineStrip()
        {
            return new LineStrip(
                Transform.TransformLocalToGlobal(this._Point1),
                Transform.TransformLocalToGlobal(this._Point2),
                Transform.TransformLocalToGlobal(this._Point3));
        }
    } // { end Class }
} // { end namespace }
